Java集合之ArrayList源码分析
1.简介
List在数据结构中表现为是线性表的方式,其元素以线性方式存储,集合中允许存放重复的对象,List接口主要的实现类有ArrayList和LinkedList。Java中分别提供了这两种结构的实现,这一篇文章是要熟悉下ArrayList的源码实现。ArrayList其实就是一组长度可变的数组,当实例化了一个ArrayList,该数据也被实例化了,当向集合中添加对象时,数组的大小也随着改变,这样它所带来的有优点是快速的随机访问,即使访问每个元素所带来的性能问题也是很小的,但缺点就是想其中添加或删除对象速度慢,当你创建的数组是不确定其容量,所以当我们改变这个数组时就必须在内存中做很多的处理,如你想要数组中任意两个元素中间添加对象,那么在内存中数组要移动所有后面的对象。使用示例如下:
package com.test.collections; import java.util.ArrayList; public class ArrayListTest { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub ArrayList<String> arrayList = new ArrayList<String>(); arrayList.add("A"); arrayList.add("B"); arrayList.add("C"); arrayList.add("D"); arrayList.add("E"); System.out.println(arrayList.add("F")); System.out.println(arrayList.contains("A")); System.out.println(arrayList.indexOf("E")); System.out.println(arrayList.contains("A")); System.out.println(arrayList.clone()); System.out.println(arrayList.lastIndexOf("D")); System.out.println(arrayList.remove(1)); System.out.println(arrayList.remove("A")); arrayList.trimToSize(); arrayList.add(1, "N"); arrayList.clear(); } }
2.继承结构
ArrayList继承了AbstractList<E>,实现了List<E>,RandomAccess, Cloneable, Serializable这几个接口,在ArrayList的内部还有私有化的内部类Itr继承实现了Iterator<E>接口,ListItr继承了ArrayList<E>.Itr 并且实现了 ListIterator<E>接口。可以使用迭代器的相关方法。
3.源码分析
a:相关属性
通过源码可以发现ArrayList实现类里面含有的属性有private static final int DEFAULT_CAPACITY = 10;private static final Object[] EMPTY_ELEMENTDATA = new Object[0];private transient Object[] elementData;private int size;private static final int MAX_ARRAY_SIZE = 2147483639;
DEFAULT_CAPACITY :缺省默认的初始化数组大小
EMPTY_ELEMENTDATA :创建一个空的初始化数组
elementData:瞬时数组记录存放数组列表的元素(transient 关键字标识该属性能和对象一起进行串行序列化)
size :标识记录List的大小
MAX_ARRAY_SIZE :数组允许的最大元素数量
b:构造方法
public ArrayList(int paramInt) { if (paramInt < 0) throw new IllegalArgumentException("Illegal Capacity: " + paramInt); this.elementData = new Object[paramInt]; } public ArrayList() { this.elementData = EMPTY_ELEMENTDATA; } public ArrayList(Collection<? extends E> paramCollection) { this.elementData = paramCollection.toArray(); this.size = this.elementData.length; if (this.elementData.getClass() == [Ljava.lang.Object.class) return; this.elementData = Arrays.copyOf(this.elementData, this.size, [Ljava.lang.Object.class); }
通过源码我们可以发现AarrayList提供了三种类型的构造函数:无参的构造函数ArrayList()是创建了一个空的ArrayList;带有一个整形参数的构造函数ArrayList(int) 等于了初始化了一个固定容量大小的ArrayList;提供了含有稽核参数的构造函数ArrayList(Collection)是通过将将该集合中的数据通过复制到新的ArrayList中并且将它赋值给elementData;
c:trimeToSize()
public void trimToSize() { this.modCount += 1; if (this.size >= this.elementData.length) return; this.elementData = Arrays.copyOf(this.elementData, this.size); }
通过源码可以发现整个方法的作用就是去除了空的未被利用的null空间。作用只是去掉预留元素位置而已,而之后的元素size是这个ArrayList存放的最小size。
d:inexOF(Object)和lastIndexOf(Object)
public int indexOf(Object paramObject) { int i; if (paramObject == null) for (i = 0; i < this.size; ++i) if (this.elementData[i] == null) return i; else for (i = 0; i < this.size; ++i) if (paramObject.equals(this.elementData[i])) return i; return -1; } public int lastIndexOf(Object paramObject) { int i; if (paramObject == null) for (i = this.size - 1; i >= 0; --i) if (this.elementData[i] == null) return i; else for (i = this.size - 1; i >= 0; --i) if (paramObject.equals(this.elementData[i])) return i; return -1; }
之所以把这两个方法放到一起分析是因为这两个方法在功能和实现上有很多类似的地方,indexOf()返回元素所在的下标位置,从第一个元素开始往后遍历,而lastIndexOf()方法则是相反的,则是最列表的最后一个元素开始遍历的 然后返回距离最后一个元素的位置。
以indexOf(Object)方法为例,通过源代码可以看到它的实现是这样的:如果传入的参数对象不为空的话,那么就开始从头到尾开始遍历这个List对象,如果所遍历到的元素对象不为空的话,那么就与传入的对象进行比较,如果比较的值相等那么就返回改比较的下标位置,如果没有找到这个元素就返回-1;
e:get(int)
public E get(int paramInt) { rangeCheck(paramInt); return elementData(paramInt); } E elementData(int paramInt) { return this.elementData[paramInt]; } private void rangeCheck(int paramInt) { if (paramInt < this.size) return; throw new IndexOutOfBoundsException(outOfBoundsMsg(paramInt)); }
这个方法是根据索引下标返回对应位置的List中的值;它首先通过rangCheck(int)这个方法判断传入的参数是否合法,然后通过elementData(int)方法返回它的值;但是我们可以发现它其实是根据数组下标直接拿到对应的值的。
f:clear()
public void clear() { this.modCount += 1; for (int i = 0; i < this.size; ++i) this.elementData[i] = null; this.size = 0; }
clear()顾名思义是把列表清空,它的实现逻辑很简单就是通过遍历然后将每个位置上的值设为null,并且把列表的长度设置为0;
g:删除元素
public E remove(int paramInt) { rangeCheck(paramInt); this.modCount += 1; Object localObject = elementData(paramInt); int i = this.size - paramInt - 1; if (i > 0) System.arraycopy(this.elementData, paramInt + 1, this.elementData, paramInt, i); this.elementData[(--this.size)] = null; return localObject; } public boolean remove(Object paramObject) { int i; if (paramObject == null) for (i = 0; i < this.size; ++i) { if (this.elementData[i] != null) continue; fastRemove(i); return true; } else for (i = 0; i < this.size; ++i) { if (!(paramObject.equals(this.elementData[i]))) continue; fastRemove(i); return true; } return false; } private void fastRemove(int paramInt) { this.modCount += 1; int i = this.size - paramInt - 1; if (i > 0) System.arraycopy(this.elementData, paramInt + 1, this.elementData, paramInt, i); this.elementData[(--this.size)] = null; }
第一个方法remove(int)是通过下标位置删除元素的,首先是判断参数是否合法,然后获取这个下标位置的元素值,通过复制将每个元素的位置后移一下,最后将该原列表的最后面元素置为null,返回被删除的对象;第二个方法是remove(Object)直接删除对象,从第一个元素开始遍历,如果遍历到了就删除返回true,如果元素不存在就返回false,第三个方法是将删除元素的方法独立了根据下标删除对象元素。
h:添加元素
public boolean add(E paramE) { ensureCapacityInternal(this.size + 1); this.elementData[(this.size++)] = paramE; return true; } public void add(int paramInt, E paramE) { rangeCheckForAdd(paramInt); ensureCapacityInternal(this.size + 1); System.arraycopy(this.elementData, paramInt, this.elementData, paramInt + 1, this.size - paramInt); this.elementData[paramInt] = paramE; this.size += 1; } public boolean addAll(Collection<? extends E> paramCollection) { Object[] arrayOfObject = paramCollection.toArray(); int i = arrayOfObject.length; ensureCapacityInternal(this.size + i); System.arraycopy(arrayOfObject, 0, this.elementData, this.size, i); this.size += i; return (i != 0); } public boolean addAll(int paramInt, Collection<? extends E> paramCollection) { rangeCheckForAdd(paramInt); Object[] arrayOfObject = paramCollection.toArray(); int i = arrayOfObject.length; ensureCapacityInternal(this.size + i); int j = this.size - paramInt; if (j > 0) System.arraycopy(this.elementData, paramInt, this.elementData, paramInt + i, j); System.arraycopy(arrayOfObject, 0, this.elementData, paramInt, i); this.size += i; return (i != 0); }
第一个和第二个方式添加一个元素,第三个和第四个方法是添加一个集合到列表中去;第一个方法是首先检查列表的大小是否合乎要求,然后直接在列表的末尾添加上元素。第二个方式在制定的位置添加上元素对象,它除了检查下标位置还有是列表的容量增加之外,就是将每个元素的位置后移,然后把空出来的位置留给我们需要插入的对象。第三个方法和第四个方法实现和第一个第二个类型,只不过把一个元素变成了一个集合。
i:contains(Object)
public boolean contains(Object paramObject) { return (indexOf(paramObject) >= 0); }
该方法是看是否含有某一元素,首先获取这个元素在列表中的位置,如果位置存在就说么含有该元素。
4.其他(小结)
ArrayList是在数组的基础了增加了一些我们开发中经常用到了方法,可是有些方法的效率也不怎么高,我们可以在具体的开发过程中视情况而定是否需要重写。不过它提供了大量的方法基本上够我们使用了。只是通过源码的解析了解一些它的底层方法的实现。